/*******************************************************************************
* Copyright (c) 2000, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.widgets;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.carbon.HMHelpContentRec;
import org.eclipse.swt.internal.carbon.OS;
import org.eclipse.swt.events.*;
/**
* Instances of this class represent popup windows that are used
* to inform or warn the user.
* <p>
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>BALLOON, ICON_ERROR, ICON_INFORMATION, ICON_WARNING</dd>
* <dt><b>Events:</b></dt>
* <dd>Selection</dd>
* </dl>
* </p><p>
* Note: Only one of the styles ICON_ERROR, ICON_INFORMATION,
* and ICON_WARNING may be specified.
* </p><p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*
* @see <a href="http://www.eclipse.org/swt/snippets/#tooltips">Tool Tips snippets</a>
* @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
*
* @since 3.2
* @noextend This class is not intended to be subclassed by clients.
*/
public class ToolTip extends Widget {
Shell parent, tip;
int x, y;
boolean spikeAbove, autohide;
TextLayout layoutText, layoutMessage;
String text, message;
TrayItem item;
Region region;
Font boldFont;
Runnable runnable;
int helpString;
static final int BORDER = 5;
static final int PADDING = 5;
static final int INSET = 4;
static final int TIP_HEIGHT = 20;
static final int IMAGE_SIZE = 16;
static final int DELAY = 10000;
/**
* Constructs a new instance of this class given its parent
* and a style value describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
* </p>
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param style the style of control to construct
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @see SWT#BALLOON
* @see SWT#ICON_ERROR
* @see SWT#ICON_INFORMATION
* @see SWT#ICON_WARNING
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public ToolTip (Shell parent, int style) {
super (parent, checkStyle (style));
this.parent = parent;
createWidget ();
parent.addToolTip (this);
}
static int checkStyle (int style) {
int mask = SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING;
if ((style & mask) == 0) return style;
return checkBits (style, SWT.ICON_INFORMATION, SWT.ICON_WARNING, SWT.ICON_ERROR, 0, 0, 0);
}
/**
* Adds the listener to the collection of listeners who will
* be notified when the receiver is selected by the user, by sending
* it one of the messages defined in the <code>SelectionListener</code>
* interface.
* <p>
* <code>widgetSelected</code> is called when the receiver is selected.
* <code>widgetDefaultSelected</code> is not called.
* </p>
*
* @param listener the listener which should be notified when the receiver is selected by the user
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener (SelectionListener listener) {
checkWidget ();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Selection,typedListener);
addListener (SWT.DefaultSelection,typedListener);
}
void configure () {
Display display = parent.getDisplay ();
int x = this.x;
int y = this.y;
if (x == -1 || y == -1) {
Point point;
if (item != null) {
point = item.getLocation ();
} else {
point = display.getCursorLocation ();
}
x = point.x;
y = point.y;
}
Monitor monitor = parent.getMonitor ();
Rectangle dest = monitor.getBounds ();
Point size = getSize (dest.width / 4);
int w = size.x;
int h = size.y;
int t = (style & SWT.BALLOON) != 0 ? TIP_HEIGHT : 0;
int i = (style & SWT.BALLOON) != 0 ? 17 : 0;
tip.setSize (w, h + t);
int [] polyline;
spikeAbove = dest.height >= y + size.y + t;
if (dest.width >= x + size.x) {
if (dest.height >= y + size.y + t) {
polyline = new int [] {
0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t, 5, 1+t, 5, t,
16, t, 16, 0, 35, t,
w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t,
w, h-5+t, w-1, h-5+t, w-1, h-3+t, w-2, h-3+t, w-2, h-2+t, w-3, h-2+t, w-3, h-1+t, w-5, h-1+t, w-5, h+t,
5, h+t, 5, h-1+t, 3, h-1+t, 3, h-2+t, 2, h-2+t, 2, h-3+t, 1, h-3+t, 1, h-5+t, 0, h-5+t,
0, 5+t};
tip.setLocation (Math.max (0, x - i), y);
} else {
polyline = new int [] {
0, 5, 1, 5, 1, 3, 3, 1, 5, 1, 5, 0,
w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5,
w, h-5, w-1, h-5, w-1, h-3, w-2, h-3, w-2, h-2, w-3, h-2, w-3, h-1, w-5, h-1, w-5, h,
35, h, 16, h+t, 16, h,
5, h, 5, h-1, 3, h-1, 3, h-2, 2, h-2, 2, h-3, 1, h-3, 1, h-5, 0, h-5,
0, 5};
tip.setLocation (Math.max (0, x - i), y - size.y - t);
}
} else {
if (dest.height >= y + size.y + t) {
polyline = new int [] {
0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t, 5, 1+t, 5, t,
w-35, t, w-16, 0, w-16, t,
w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t,
w, h-5+t, w-1, h-5+t, w-1, h-3+t, w-2, h-3+t, w-2, h-2+t, w-3, h-2+t, w-3, h-1+t, w-5, h-1+t, w-5, h+t,
5, h+t, 5, h-1+t, 3, h-1+t, 3, h-2+t, 2, h-2+t, 2, h-3+t, 1, h-3+t, 1, h-5+t, 0, h-5+t,
0, 5+t};
tip.setLocation (Math.min (dest.width - size.x, x - size.x + i), y);
} else {
polyline = new int [] {
0, 5, 1, 5, 1, 3, 3, 1, 5, 1, 5, 0,
w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5,
w, h-5, w-1, h-5, w-1, h-3, w-2, h-3, w-2, h-2, w-3, h-2, w-3, h-1, w-5, h-1, w-5, h,
w-16, h, w-16, h+t, w-35, h,
5, h, 5, h-1, 3, h-1, 3, h-2, 2, h-2, 2, h-3, 1, h-3, 1, h-5, 0, h-5,
0, 5};
tip.setLocation (Math.min (dest.width - size.x, x - size.x + i), y - size.y - t);
}
}
if ((style & SWT.BALLOON) != 0) {
if (region != null) region.dispose ();
region = new Region (display);
region.add (polyline);
tip.setRegion (region);
}
}
void createWidget () {
super.createWidget ();
this.autohide = true;
x = y = -1;
text = "";
message = "";
}
void disposeTip () {
if (tip != null) tip.dispose ();
tip = null;
if (region != null) region.dispose ();
region = null;
if (layoutText != null) layoutText.dispose ();
layoutText = null;
if (layoutMessage != null) layoutMessage.dispose ();
layoutMessage = null;
if (boldFont != null) boldFont.dispose ();
boldFont = null;
}
/**
* Returns <code>true</code> if the receiver is automatically
* hidden by the platform, and <code>false</code> otherwise.
*
* @return the receiver's auto hide state
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
*/
public boolean getAutoHide () {
checkWidget ();
return autohide;
}
/**
* Returns the receiver's message, which will be an empty
* string if it has never been set.
*
* @return the receiver's message
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public String getMessage () {
checkWidget ();
return message;
}
/**
* Returns the receiver's parent, which must be a <code>Shell</code>.
*
* @return the receiver's parent
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public Shell getParent () {
checkWidget ();
return parent;
}
Point getSize (int maxWidth) {
int textWidth = 0, messageWidth = 0;
if (layoutText != null) {
layoutText.setWidth (-1);
textWidth = layoutText.getBounds ().width;
}
if (layoutMessage != null) {
layoutMessage.setWidth (-1);
messageWidth = layoutMessage.getBounds ().width;
}
int messageTrim = 2 * INSET + 2 * BORDER + 2 * PADDING;
boolean hasImage = layoutText != null && (style & SWT.BALLOON) != 0 && (style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING)) != 0;
int textTrim = messageTrim + (hasImage ? IMAGE_SIZE : 0);
int width = Math.min (maxWidth, Math.max (textWidth + textTrim, messageWidth + messageTrim));
int textHeight = 0, messageHeight = 0;
if (layoutText != null) {
layoutText.setWidth (maxWidth - textTrim);
textHeight = layoutText.getBounds ().height;
}
if (layoutMessage != null) {
layoutMessage.setWidth (maxWidth - messageTrim);
messageHeight = layoutMessage.getBounds ().height;
}
int height = 2 * BORDER + 2 * PADDING + messageHeight;
if (layoutText != null) height += Math.max (IMAGE_SIZE, textHeight) + 2 * PADDING;
return new Point (width, height);
}
/**
* Returns the receiver's text, which will be an empty
* string if it has never been set.
*
* @return the receiver's text
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public String getText () {
checkWidget ();
return text;
}
/**
* Returns <code>true</code> if the receiver is visible, and
* <code>false</code> otherwise.
* <p>
* If one of the receiver's ancestors is not visible or some
* other condition makes the receiver not visible, this method
* may still indicate that it is considered visible even though
* it may not actually be showing.
* </p>
*
* @return the receiver's visibility state
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public boolean getVisible () {
checkWidget ();
if (tip != null) return tip.getVisible ();
if (display.helpWidget == this) {
int window = OS.FrontWindow ();
int [] windowClass = new int [1];
OS.GetWindowClass (window, windowClass);
return windowClass [0] == OS.kHelpWindowClass;
}
return false;
}
/**
* Returns <code>true</code> if the receiver is visible and all
* of the receiver's ancestors are visible and <code>false</code>
* otherwise.
*
* @return the receiver's visibility state
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see #getVisible
*/
public boolean isVisible () {
checkWidget ();
return getVisible ();
}
void onMouseDown (Event event) {
sendSelectionEvent (SWT.Selection, null, true);
setVisible (false);
}
void onPaint (Event event) {
GC gc = event.gc;
int x = BORDER + PADDING;
int y = BORDER + PADDING;
if ((style & SWT.BALLOON) != 0 && spikeAbove) y += TIP_HEIGHT;
if (layoutText != null) {
int id = style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING);
if ((style & SWT.BALLOON) != 0 && id != 0) {
Display display = getDisplay ();
Image image = display.getSystemImage (id);
Rectangle rect = image.getBounds ();
gc.drawImage (image, 0, 0, rect.width, rect.height, x, y, IMAGE_SIZE, IMAGE_SIZE);
x += IMAGE_SIZE;
}
x += INSET;
layoutText.draw (gc, x, y);
y += 2 * PADDING + Math.max (IMAGE_SIZE, layoutText.getBounds ().height);
}
if (layoutMessage != null) {
x = BORDER + PADDING + INSET;
layoutMessage.draw (gc, x, y);
}
}
void releaseWidget () {
super.releaseWidget ();
if (parent != null) parent.removeTooTip (this);
if (runnable != null) {
Display display = getDisplay ();
display.timerExec (-1, runnable);
}
runnable = null;
disposeTip ();
if (helpString != 0) OS.CFRelease (helpString);
helpString = 0;
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the receiver is selected by the user.
*
* @param listener the listener which should no longer be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #addSelectionListener
*/
public void removeSelectionListener (SelectionListener listener) {
checkWidget();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
if (eventTable == null) return;
eventTable.unhook (SWT.Selection, listener);
eventTable.unhook (SWT.DefaultSelection,listener);
}
/**
* Makes the receiver hide automatically when <code>true</code>,
* and remain visible when <code>false</code>.
*
* @param autoHide the auto hide state
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see #getVisible
* @see #setVisible
*/
public void setAutoHide (boolean autohide) {
checkWidget ();
this.autohide = autohide;
//TODO - update when visible
}
/**
* Sets the location of the receiver, which must be a tooltip,
* to the point specified by the arguments which are relative
* to the display.
* <p>
* Note that this is different from most widgets where the
* location of the widget is relative to the parent.
* </p>
*
* @param x the new x coordinate for the receiver
* @param y the new y coordinate for the receiver
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setLocation (int x, int y) {
checkWidget ();
if (this.x == x && this.y == y) return;
this.x = x;
this.y = y;
if (tip != null && tip.getVisible ()) configure ();
}
/**
* Sets the location of the receiver, which must be a tooltip,
* to the point specified by the argument which is relative
* to the display.
* <p>
* Note that this is different from most widgets where the
* location of the widget is relative to the parent.
* </p><p>
* Note that the platform window manager ultimately has control
* over the location of tooltips.
* </p>
*
* @param location the new location for the receiver
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the point is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setLocation (Point location) {
checkWidget ();
if (location == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
setLocation (location.x, location.y);
}
/**
* Sets the receiver's message.
*
* @param string the new message
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the text is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setMessage (String string) {
checkWidget ();
if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
message = string;
if (tip != null) {
layoutMessage.setText (string);
if (tip.getVisible ()) configure ();
}
}
/**
* Sets the receiver's text.
*
* @param string the new text
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the text is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setText (String string) {
checkWidget ();
if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
text = string;
if (tip != null) {
layoutText.setText (string);
TextStyle style = new TextStyle (boldFont, null, null);
layoutText.setStyle (style, 0, string.length ());
if (tip.getVisible ()) configure ();
}
}
/**
* Marks the receiver as visible if the argument is <code>true</code>,
* and marks it invisible otherwise.
* <p>
* If one of the receiver's ancestors is not visible or some
* other condition makes the receiver not visible, marking
* it visible may not actually cause it to be displayed.
* </p>
*
* @param visible the new visibility state
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setVisible (boolean visible) {
checkWidget ();
if (helpString != 0) OS.CFRelease (helpString);
helpString = 0;
if (runnable != null) display.timerExec (-1, runnable);
runnable = null;
if (visible) {
OS.HMHideTag ();
display.helpWidget = this;
if (!autohide || (style & SWT.BALLOON) != 0) {
// Show tip
if (tip == null) {
tip = new Shell (parent, SWT.ON_TOP | SWT.NO_TRIM | SWT.TOOL);
Color background = display.getSystemColor (SWT.COLOR_INFO_BACKGROUND);
tip.setBackground (background);
Listener listener = new Listener () {
public void handleEvent (Event event) {
switch (event.type) {
case SWT.Paint: onPaint (event); break;
case SWT.MouseDown: onMouseDown (event); break;
}
}
};
tip.addListener (SWT.Paint, listener);
tip.addListener (SWT.MouseDown, listener);
layoutText = new TextLayout (display);
layoutText.setText (text);
Font font = display.getSystemFont ();
FontData data = font.getFontData () [0];
boldFont = new Font (display, data.getName (), data.getHeight (), SWT.BOLD);
TextStyle style = new TextStyle (boldFont, null, null);
layoutText.setStyle (style, 0, text.length ());
layoutMessage = new TextLayout (display);
layoutMessage.setText(message);
}
configure ();
tip.setVisible (true);
if (autohide) {
runnable = new Runnable () {
public void run () {
if (!isDisposed ()) setVisible (false);
}
};
display.timerExec(DELAY, runnable);
}
} else {
// Show HMTag
if (tip != null) disposeTip ();
if (x == -1 || y == -1) {
Point point;
if (item != null) {
point = item.getLocation ();
x = point.x;
y = point.y;
} else {
org.eclipse.swt.internal.carbon.Point pt = new org.eclipse.swt.internal.carbon.Point ();
OS.GetGlobalMouse (pt);
x = pt.h;
y = pt.v;
}
}
StringBuffer string = new StringBuffer (text);
if (text.length () > 0) string.append ("\n\n");
string.append (message);
char [] buffer = new char [string.length ()];
string.getChars (0, buffer.length, buffer, 0);
helpString = OS.CFStringCreateWithCharacters (OS.kCFAllocatorDefault, buffer, buffer.length);
HMHelpContentRec helpContent = new HMHelpContentRec ();
helpContent.tagSide = (short) OS.kHMAbsoluteCenterAligned;
helpContent.absHotRect_left = (short)x;
helpContent.absHotRect_top = (short)y;
helpContent.absHotRect_right = (short)(x + 1);
helpContent.absHotRect_bottom = (short) (y + 1);
helpContent.content0_contentType = OS.kHMCFStringContent;
helpContent.content0_tagCFString = helpString;
helpContent.content1_contentType = OS.kHMCFStringContent;
helpContent.content1_tagCFString = helpString;
OS.HMDisplayTag(helpContent);
}
} else {
if (display.helpWidget == this) {
display.helpWidget = null;
OS.HMHideTag ();
if (tip != null) tip.setVisible (false);
}
}
}
}